/*
 Live.js - One script closer to Designing in the Browser
 Written for Handcraft.com by Martin Kool (@mrtnkl).

 Version 4.
 Recent change: Made stylesheet and mimetype checks case insensitive.

 http://livejs.com
 http://livejs.com/license (MIT)
 @livejs

 Include live.js#css to monitor css changes only.
 Include live.js#js to monitor js changes only.
 Include live.js#html to monitor html changes only.
 Mix and match to monitor a preferred combination such as live.js#html,css

 By default, just include live.js to monitor all css, js and html changes.

 Live.js can also be loaded as a bookmarklet. It is best to only use it for CSS then,
 as a page reload due to a change in html or css would not re-include the bookmarklet.
 To monitor CSS and be notified that it has loaded, include it as: live.js#css,notify
*/
(function () {

    var headers = { "Etag": 1, "Last-Modified": 1, "Content-Length": 1, "Content-Type": 1 },
        resources = {},
        pendingRequests = {},
        currentLinkElements = {},
        oldLinkElements = {},
        interval = 1000,
        loaded = false,
        active = { "html": 1, "css": 1, "js": 1 };

    var Live = {

        // performs a cycle per interval
        heartbeat: function () {
            if (document.body) {
                // make sure all resources are loaded on first activation
                if (!loaded) Live.loadresources();
                Live.checkForChanges();
            }
            setTimeout(Live.heartbeat, interval);
        },

        // loads all local css and js resources upon first activation
        loadresources: function () {

            // helper method to assert if a given url is local
            function isLocal(url) {
                var loc = document.location,
                    reg = new RegExp("^\\.|^\/(?!\/)|^[\\w]((?!://).)*$|" + loc.protocol + "//" + loc.host);
                return url.match(reg);
            }

            // gather all resources
            var scripts = document.getElementsByTagName("script"),
                links = document.getElementsByTagName("link"),
                uris = [];

            // track local js urls
            for (var i = 0; i < scripts.length; i++) {
                var script = scripts[i], src = script.getAttribute("src");
                if (src && isLocal(src))
                    uris.push(src);
                if (src && src.match(/\blive.js#/)) {
                    for (var type in active)
                        active[type] = src.match("[#,|]" + type) != null
                    if (src.match("notify"))
                        alert("Live.js is loaded.");
                }
            }
            if (!active.js) uris = [];
            if (active.html) uris.push(document.location.href);

            // track local css urls
            for (var i = 0; i < links.length && active.css; i++) {
                var link = links[i], rel = link.getAttribute("rel"), href = link.getAttribute("href", 2);
                if (href && rel && rel.match(new RegExp("stylesheet", "i")) && isLocal(href)) {
                    uris.push(href);
                    currentLinkElements[href] = link;
                }
            }

            // initialize the resources info
            for (var i = 0; i < uris.length; i++) {
                var url = uris[i];
                Live.getHead(url, function (url, info) {
                    resources[url] = info;
                });
            }

            // add rule for morphing between old and new css files
            var head = document.getElementsByTagName("head")[0],
                style = document.createElement("style"),
                rule = "transition: all .3s ease-out;"
            css = [".livejs-loading * { ", rule, " -webkit-", rule, "-moz-", rule, "-o-", rule, "}"].join('');
            style.setAttribute("type", "text/css");
            head.appendChild(style);
            style.styleSheet ? style.styleSheet.cssText = css : style.appendChild(document.createTextNode(css));

            // yep
            loaded = true;
        },

        // check all tracking resources for changes
        checkForChanges: function () {
            for (var url in resources) {
                if (pendingRequests[url])
                    continue;

                Live.getHead(url, function (url, newInfo) {
                    var oldInfo = resources[url],
                        hasChanged = false;
                    resources[url] = newInfo;
                    for (var header in oldInfo) {
                        // do verification based on the header type
                        var oldValue = oldInfo[header],
                            newValue = newInfo[header],
                            contentType = newInfo["Content-Type"];
                        switch (header.toLowerCase()) {
                            case "etag":
                                if (!newValue) break;
                            // fall through to default
                            default:
                                hasChanged = oldValue != newValue;
                                break;
                        }
                        // if changed, act
                        if (hasChanged) {
                            Live.refreshResource(url, contentType);
                            break;
                        }
                    }
                });
            }
        },

        // act upon a changed url of certain content type
        refreshResource: function (url, type) {
            switch (type.toLowerCase()) {
                // css files can be reloaded dynamically by replacing the link element
                case "text/css":
                    var link = currentLinkElements[url],
                        html = document.body.parentNode,
                        head = link.parentNode,
                        next = link.nextSibling,
                        newLink = document.createElement("link");

                    html.className = html.className.replace(/\s*livejs\-loading/gi, '') + ' livejs-loading';
                    newLink.setAttribute("type", "text/css");
                    newLink.setAttribute("rel", "stylesheet");
                    newLink.setAttribute("href", url + "?now=" + new Date() * 1);
                    next ? head.insertBefore(newLink, next) : head.appendChild(newLink);
                    currentLinkElements[url] = newLink;
                    oldLinkElements[url] = link;

                    // schedule removal of the old link
                    Live.removeoldLinkElements();
                    break;

                // check if an html resource is our current url, then reload
                case "text/html":
                    if (url != document.location.href)
                        return;

                // local javascript changes cause a reload as well
                case "text/javascript":
                case "application/javascript":
                case "application/x-javascript":
                    document.location.reload();
            }
        },

        // removes the old stylesheet rules only once the new one has finished loading
        removeoldLinkElements: function () {
            var pending = 0;
            for (var url in oldLinkElements) {
                // if this sheet has any cssRules, delete the old link
                try {
                    var link = currentLinkElements[url],
                        oldLink = oldLinkElements[url],
                        html = document.body.parentNode,
                        sheet = link.sheet || link.styleSheet,
                        rules = sheet.rules || sheet.cssRules;
                    if (rules.length >= 0) {
                        oldLink.parentNode.removeChild(oldLink);
                        delete oldLinkElements[url];
                        setTimeout(function () {
                            html.className = html.className.replace(/\s*livejs\-loading/gi, '');
                        }, 100);
                    }
                } catch (e) {
                    pending++;
                }
                if (pending) setTimeout(Live.removeoldLinkElements, 50);
            }
        },

        // performs a HEAD request and passes the header info to the given callback
        getHead: function (url, callback) {
            pendingRequests[url] = true;
            var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XmlHttp");
            xhr.open("HEAD", url, true);
            xhr.onreadystatechange = function () {
                delete pendingRequests[url];
                if (xhr.readyState == 4 && xhr.status != 304) {
                    xhr.getAllResponseHeaders();
                    var info = {};
                    for (var h in headers) {
                        var value = xhr.getResponseHeader(h);
                        // adjust the simple Etag variant to match on its significant part
                        if (h.toLowerCase() == "etag" && value) value = value.replace(/^W\//, '');
                        if (h.toLowerCase() == "content-type" && value) value = value.replace(/^(.*?);.*?$/i, "$1");
                        info[h] = value;
                    }
                    callback(url, info);
                }
            }
            xhr.send();
        }
    };

    // start listening
    if (document.location.protocol != "file:") {
        if (!window.liveJsLoaded)
            Live.heartbeat();

        window.liveJsLoaded = true;
    }
    else if (window.console)
        console.log("Live.js doesn't support the file protocol. It needs http.");
})();